package orm

import (
	"context"
	"reflect"

	"gitee.com/geektime-geekbang/geektime-go/orm/homework2/internal/errs"
)

type Updater[T any] struct {
	builder
	db      *DB
	assigns []Assignable
	val     *T
	where   []Predicate
}

func NewUpdater[T any](db *DB) *Updater[T] {
	return &Updater[T]{
		builder: builder{
			dialect: db.dialect,
			quoter:  db.dialect.quoter(),
		},
		db: db,
	}
}

func (u *Updater[T]) Update(t *T) *Updater[T] {
	u.val = t
	return u
}

func (u *Updater[T]) Set(assigns ...Assignable) *Updater[T] {
	u.assigns = assigns
	return u
}

func (u *Updater[T]) Build() (*Query, error) {
	if u.val == nil && u.assigns == nil {
		return nil, errs.ErrNoUpdatedColumns
	}
	query := &Query{}
	u.db.r.Get(u.val)
	m, err := u.db.r.Get(u.val)
	if err != nil {
		return nil, err
	}
	u.model = m
	u.sb.WriteString("UPDATE ")
	u.quote(m.TableName)
	u.sb.WriteString(" SET ")
	refVal := u.db.valCreator(u.val, u.model)
	for index, assign := range u.assigns {
		if index > 0 {
			u.sb.WriteString(",")
		}
		switch d := assign.(type) {
		case Column:
			u.quote(m.FieldMap[d.name].ColName)
			u.sb.WriteString("=?")
			fdVal, err := refVal.Field(d.name)
			if err != nil {
				return nil, err
			}
			query.Args = append(query.Args, fdVal)
		case Assignment:
			u.quote(m.FieldMap[d.column].ColName)
			u.sb.WriteString("=")
			switch valtype := d.val.(type) {
			case value:
				u.sb.WriteString("?")
				query.Args = append(query.Args, valtype.val)
			case MathExpr:
				switch lv := valtype.left.(type) {
				case Column:
					u.quote(m.FieldMap[lv.name].ColName)
				}
				u.sb.WriteString(" " + valtype.op.String() + " ?")
				switch rv := valtype.right.(type) {
				case value:
					query.Args = append(query.Args, rv.val)
				}
			case RawExpr:
				u.sb.WriteString(valtype.raw)
				query.Args = append(query.Args, valtype.args...)
			}
		}
	}
	if u.where != nil {
		u.sb.WriteString(" WHERE ")
		for index, where := range u.where {
			if index > 0 {
				u.sb.WriteString(" AND ")
			}
			switch lv := where.left.(type) {
			case Column:
				u.quote(m.FieldMap[lv.name].ColName)
			}
			u.sb.WriteString(" " + where.op.String() + " ?")
			switch rv := where.right.(type) {
			case value:
				query.Args = append(query.Args, rv.val)
			}
		}
	}
	u.sb.WriteString(";")
	query.SQL = u.sb.String()
	return query, nil
}

func (u *Updater[T]) Where(ps ...Predicate) *Updater[T] {
	u.where = ps
	return u
}

func (u *Updater[T]) Exec(ctx context.Context) Result {
	q, err := u.Build()
	if err != nil {
		return Result{err: err}
	}
	res, err := u.db.db.ExecContext(ctx, q.SQL, q.Args...)
	return Result{err: err, res: res}
}

// AssignNotZeroColumns 更新非零值
func AssignNotZeroColumns(entity interface{}) []Assignable {
	result := make([]Assignable, 0)
	t := reflectType(entity)
	v := reflectValue(entity)
	for i := 0; i < v.NumField(); i++ {
		if !v.Field(i).IsZero() {
			a := Assignment{}
			a.column = t.Field(i).Name
			a.val = value{val: v.Field(i).Interface()}
			result = append(result, a)
		}
	}
	return result
}

func reflectType(entity interface{}) reflect.Type {
	if t := reflect.TypeOf(entity); t.Kind() == reflect.Ptr {
		return t.Elem()
	} else {
		return t
	}
}

func reflectValue(entity interface{}) reflect.Value {
	if t := reflect.ValueOf(entity); t.Kind() == reflect.Ptr {
		return t.Elem()
	} else {
		return t
	}
}
